/*

bl2 for cv1800b, T-HEAD C906.

*/



#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <setjmp.h>

#include "soc.h"

#include "dbg_cli.h"
#include "fatfs.h"


typedef unsigned long ulong;
typedef unsigned long vu_long;


ulong mem_test_alt( vu_long *buf, ulong start_addr, ulong end_addr, vu_long *dummy )
{
	vu_long *addr;
	ulong errs = 0;
	ulong val, readback;
	int j;
	vu_long offset;
	vu_long test_offset;
	vu_long pattern;
	vu_long temp;
	vu_long anti_pattern;
	vu_long num_words;

	static const ulong bitpattern[] = {
		0x00000001,	/* single bit */
		0x00000003,	/* two adjacent bits */
		0x00000007,	/* three adjacent bits */
		0x0000000F,	/* four adjacent bits */
		0x00000005,	/* two non-adjacent bits */
		0x00000015,	/* three non-adjacent bits */
		0x00000055,	/* four non-adjacent bits */
		0xaaaaaaaa,	/* alternating 1/0 */
	};

	num_words = (end_addr - start_addr) / sizeof(vu_long);

	/*
	 * Data line test: write a pattern to the first
	 * location, write the 1's complement to a 'parking'
	 * address (changes the state of the data bus so a
	 * floating bus doesn't give a false OK), and then
	 * read the value back. Note that we read it back
	 * into a variable because the next time we read it,
	 * it might be right (been there, tough to explain to
	 * the quality guys why it prints a failure when the
	 * "is" and "should be" are obviously the same in the
	 * error message).
	 *
	 * Rather than exhaustively testing, we test some
	 * patterns by shifting '1' bits through a field of
	 * '0's and '0' bits through a field of '1's (i.e.
	 * pattern and ~pattern).
	 */
	addr = buf;
	for ( j = 0; j < sizeof(bitpattern) / sizeof(bitpattern[0]); j++ )  {
		
		val = bitpattern[j];
		for (; val != 0; val <<= 1) {
			
			*addr = val;
			*dummy  = ~val; /* clear the test data off the bus */
			readback = *addr;
			
			if ( readback != val )  {
				printf( "FAILURE (data line): expected %08lx, actual %08lx\n", val, readback );
				errs++;
			}

			*addr  = ~val;
			*dummy  = val;
			readback = *addr;
			if ( readback != ~val )  {
				printf( "FAILURE (data line): Is %08lx, should be %08lx\n", readback, ~val );
				errs++;
			}
		}
	}

	/*
	 * Based on code whose Original Author and Copyright
	 * information follows: Copyright (c) 1998 by Michael
	 * Barr. This software is placed into the public
	 * domain and may be used for any purpose. However,
	 * this notice must not be changed or removed and no
	 * warranty is either expressed or implied by its
	 * publication or distribution.
	 */

	/*
	* Address line test

	 * Description: Test the address bus wiring in a
	 *              memory region by performing a walking
	 *              1's test on the relevant bits of the
	 *              address and checking for aliasing.
	 *              This test will find single-bit
	 *              address failures such as stuck-high,
	 *              stuck-low, and shorted pins. The base
	 *              address and size of the region are
	 *              selected by the caller.

	 * Notes:	For best results, the selected base
	 *              address should have enough LSB 0's to
	 *              guarantee single address bit changes.
	 *              For example, to test a 64-Kbyte
	 *              region, select a base address on a
	 *              64-Kbyte boundary. Also, select the
	 *              region size as a power-of-two if at
	 *              all possible.
	 *
	 * Returns:     0 if the test succeeds, 1 if the test fails.
	 */
	pattern = (vu_long) 0xaaaaaaaa;
	anti_pattern = (vu_long) 0x55555555;

	printf( "%s:%d: length = 0x%.8lx\n", __func__, __LINE__, num_words );

	/*
	 * Write the default pattern at each of the
	 * power-of-two offsets.
	 */
	for (offset = 1; offset < num_words; offset <<= 1)
		addr[offset] = pattern;

	/*
	 * Check for address bits stuck high.
	 */
	test_offset = 0;
	addr[test_offset] = anti_pattern;

	for (offset = 1; offset < num_words; offset <<= 1) {
		temp = addr[offset];
		if (temp != pattern) {
			printf("\nFAILURE: Address bit stuck high @ 0x%.8lx: expected 0x%.8lx, actual 0x%.8lx\n",
				start_addr + offset*sizeof(vu_long), pattern, temp);
			errs++;
		}
	}

	addr[test_offset] = pattern;

	/*
	 * Check for addr bits stuck low or shorted.
	 */
	for (test_offset = 1; test_offset < num_words; test_offset <<= 1) {
		addr[test_offset] = anti_pattern;

		for (offset = 1; offset < num_words; offset <<= 1) {
			temp = addr[offset];
			if ((temp != pattern) && (offset != test_offset)) {
				printf("\nFAILURE: Address bit stuck low or shorted @ 0x%.8lx: expected 0x%.8lx, actual 0x%.8lx\n",
					start_addr + offset*sizeof(vu_long), pattern, temp );
				errs++;
			}
		}
		addr[test_offset] = pattern;
	}

	/*
	 * Description: Test the integrity of a physical
	 *		memory device by performing an
	 *		increment/decrement test over the
	 *		entire region. In the process every
	 *		storage bit in the device is tested
	 *		as a zero and a one. The base address
	 *		and the size of the region are
	 *		selected by the caller.
	 *
	 * Returns:     0 if the test succeeds, 1 if the test fails.
	 */
	num_words++;

	/*
	 * Fill memory with a known pattern.
	 */
	for (pattern = 1, offset = 0; offset < num_words; pattern++, offset++) {
		addr[offset] = pattern;
	}

	/*
	 * Check each location and invert it for the second pass.
	 */
	for (pattern = 1, offset = 0; offset < num_words; pattern++, offset++) {
		temp = addr[offset];
		if (temp != pattern) {
			printf("\nFAILURE (read/write) @ 0x%.8lx: expected 0x%.8lx, actual 0x%.8lx)\n",
				start_addr + offset*sizeof(vu_long), pattern, temp );
			errs++;
		}

		anti_pattern = ~pattern;
		addr[offset] = anti_pattern;
	}

	/*
	 * Check each location for the inverted pattern and zero it.
	 */
	for (pattern = 1, offset = 0; offset < num_words; pattern++, offset++) {

		anti_pattern = ~pattern;
		temp = addr[offset];
		if (temp != anti_pattern) {
			printf("\nFAILURE (read/write): @ 0x%.8lx: expected 0x%.8lx, actual 0x%.8lx)\n",
				start_addr + offset*sizeof(vu_long), anti_pattern, temp );
			errs++;
		}

		addr[offset] = 0;
	}

	return errs;
}




#if 0

void  test_sleep( uint32_t ticks )
{
	uint64_t tlast;
	uint64_t tcurr;

	tlast = __get_MCYCLE();

	while ( true )  {
		tcurr = __get_MCYCLE();

		tcurr = tcurr - tlast;
		if ( tcurr >= ticks )  {
			
			break;

		} else {
			asm volatile ( "nop" );
			asm volatile ( "nop" );
		}
	}
}

#endif


void  test_sleep( uint32_t ticks )
{
	for ( uint32_t i=0; i<ticks; i++ )  {
		asm volatile ( "nop" );
	}
}






static inline uint32_t read_reg( uintptr_t addr )
{
	return (*((volatile uint32_t *) (addr)));
}


static inline void write_reg( uintptr_t addr, uint32_t value )
{
	*(volatile uint32_t *)(addr) = value;
	return;
}




int bl2_dbg_gettime( void * pctx, int argc, const char * argv[] )
{
	uint32_t temp;
	temp = __get_mtime();
	printf( ":%lu\n", temp );
	return 0;
}

extern uint32_t p_rom_api_image_crc(const void *buf, int len);


int bl2_dbg_crc32( void * pctx, int argc, const char * argv[] )
{
	int  iret;
	uint32_t  addr;
	uint32_t  tcnt;	
	uint32_t temp;
	
	/**/
	if ( argc != 3 )
	{
		goto usage;
	}
	
	/**/
	iret = debug_str2uint( argv[1], &addr );
	if ( iret != 0 )
	{
		printf( "addr fmt error..%d.. \n", iret );
		goto usage;
	}
		
	/**/
	iret = debug_str2uint( argv[2], &tcnt );
	if ( iret != 0 ) 
	{
		printf( "cnt fmt error..%d.. \n", iret );
		return 2;
	}	

	temp = p_rom_api_image_crc( (void *)(uintptr_t)addr, tcnt );
	printf( "0x%lx, %lu : 0x%lx\n", addr, tcnt, temp );
	return 0;
		
usage:
	printf("usage: %s <addr> <cnt>\n", argv[0] );
	return 0;
}


int bl2_dbg_gogo( void * pctx, int argc, const char * argv[] )
{
	int iret;
	uint32_t temp;
	int64_t tbuf[38];

	/**/
	if ( argc < 2 )
	{
		printf( "default jump to 0x8000-0000\n" );
		temp = 0x80000000;

	}  else {

		/**/
		iret = debug_str2uint( argv[1], &temp );
		if ( iret != 0 )
		{
			printf( "addr fmt error..%d.. \n", iret );
			goto usage;
		}

	}
	

	/**/
	__DCACHE_CALL();
	__ICACHE_IALL();
	__ISB();

#if 0
	/* [0]:s0, [1]:lr, [2]:sp, [3]=rsvd */
    /* 可以使用 [0] 带一个参数值, 交给下一个阶段运行的程序. */
    tbuf[0] = 0x12345678;
    tbuf[1] = temp;
    tbuf[2] = 0x80200000;
    tbuf[3] = 0;

    /* 后面的参数 1 没有任何作用. */
	// __builtin_longjmp( tbuf , 1 );
#endif

	/* [0] = PC,  [13] = SP */
	tbuf[0] = (int64_t)(uint64_t)temp;
	tbuf[13] = (int64_t)(uint64_t)0x80200000;
	longjmp( tbuf, 1 );
	
usage:
	printf( "%s <addr>\n", argv[0] );
	return 0;
}




int bl2_dbg_fatfs( void * pctx, int argc, const char * argv[] )
{
	int iret;
	uint32_t temp;
	uint32_t tsiz;
	sdctx_t sdinfo;
	fatfs_t fsinfo;

	if ( argc < 3 )  {
		goto usage;
	}
	
	iret = debug_str2uint( argv[2], &temp );
	if ( iret != 0 )  {
		printf( "args fmt err, %d\n", iret );
		goto usage;
	}

	/**/
	iret = sdcard_init( 0x4310000, &sdinfo );
	if ( iret != 0 )  {
		printf( "sdcard init fail, %d\n", iret );
		return 400;
	}

	//
	iret = fatfs_init( &sdinfo, &fsinfo );
	if ( iret != 0 )  {
		printf( "fatfs info fail, %d\n", iret );
		return 1;
	}

	iret = fatfs_fstat( &fsinfo, argv[1], &tsiz );
	if ( iret != 0 )  {
		printf( "fstat iret = %d\n", iret );
		return 2;
	}
	
	printf( "fstat ok, size=%lu\n", tsiz );
	if ( tsiz <= 0 )  {
		return 3;
	}

	iret = fatfs_fread( &fsinfo, argv[1], (uint8_t *)(uintptr_t)temp );
	if ( iret <= 0 )  {
		printf( "fread iret = %d\n", iret );
		return 3;
	}

	/* special for small c906 */
	write_reg( 0x0E000040, temp );
	write_reg( 0x0E000044, tsiz );
	write_reg( 0x0E00004C, 0x87654321 );
	
	printf( "read succ..\n" );
	return 0;

usage:
	printf( "usage:\n" );
	printf( "\t%s <fname> <addr>\n", argv[0] );
	return 0;
}


int check_autoboot( dw_uart_regs_t * puart )
{
	int iret;
	uint32_t tsiz;
	sdctx_t sdinfo;
	fatfs_t fsinfo;
	int64_t tbuf[38];

	/**/
	iret = sdcard_init( 0x4310000, &sdinfo );
	if ( iret != 0 )  {
		printf( "sdcard init fail, %d\n", iret );
		return 400;
	}

	// 尝试从 sdcard 读取 bin 文件, 加载到 ddr 0x80000000 地址上面.
	iret = fatfs_init( &sdinfo, &fsinfo );
	if ( iret != 0 )  {
		printf( "fatfs info fail, %d\n", iret );
		return 401;
	}

	iret = fatfs_fstat( &fsinfo, "bbbb.bin", &tsiz );
	if ( iret != 0 )  {
		printf( "fstat iret = %d\n", iret );
		return 402;
	}
	
	printf( "fstat ok, size=%lu\n", tsiz );
	if ( tsiz <= 0 )  {
		return 403;
	}

	iret = fatfs_fread( &fsinfo, "bbbb.bin", (uint8_t *)(uintptr_t)0x80000000 );
	if ( iret <= 0 )  {
		printf( "fread iret = %d\n", iret );
		return 404;
	}

	// gogo run at 0x80000000
	__DCACHE_CALL();
	__ICACHE_IALL();
	__ISB();

	/* [0] = PC,  [13] = SP */
	tbuf[0] = (int64_t)(uint64_t)0x80000000;
	tbuf[13] = (int64_t)(uint64_t)0x80200000;
	longjmp( tbuf, 1 );
	return 0;
}



extern void read_ddr_pkg_info( void );
extern void ddr_sys_bring_up( void );

extern int zmodem_cmd_dump( void * pctx, int argc, const char **argv );
extern int zmodem_cmd_recv( void * pctx, int argc, const char **argv );


int bl2_main( void )
{
	cv_top_regs_t * ptop;
	char spad[128];
	int iret;	
	dw_uart_regs_t * puart;

	/**/
	ptop = (cv_top_regs_t *)(uintptr_t)0x03000000;
	ptop->GP_REG[0] = 0xB200F000;
	
    /* reset */
    puart = (dw_uart_regs_t *)(uintptr_t)0x04140000;
    puart->SRR = 0x7;
    test_sleep( 100 );
    
    /**/
    puart->LCR = 0x83;
	puart->DLL = 14;
	puart->DLH = 0;
	puart->LCR = 0x03;
	puart->FCR = 0x51;
	__DSB();

	/**/
	printf( "\n\n" );
	printf( "fsbl...\n" );
	read_ddr_pkg_info();
	ddr_sys_bring_up();

	/* check uart input for auto boot */
	iret = check_autoboot( puart );
	printf( "autoboot = %d\n", iret );

	/**/
	debug_cli_init();
	debug_add_cmd( "crc32", bl2_dbg_crc32, NULL );
	debug_add_cmd( "time", bl2_dbg_gettime, NULL );
	debug_add_cmd( "rz", zmodem_cmd_recv, NULL );
	debug_add_cmd( "gogo", bl2_dbg_gogo, NULL );
	debug_add_cmd( "fatfs", bl2_dbg_fatfs, NULL );

	/**/
	while ( true )  {
		/**/
		// temp = getchar();
		// printf( ":: %x\n", temp );

		/* read line */
		iret = cons_readline( ">> ", 120, spad );
		if ( iret != 0 ) {
			continue;
		}
		
		/* proc */
		debug_proc_line( spad );
	}

	return 0;
}


__attribute__((__noreturn__))  void panic_handler( void )
{
	uint64_t mexppc;
	uint64_t mcause;

	mexppc = __get_MEPC();
	mcause = __get_MCAUSE();

	printf( "panic haldler...\n" );
	
	printf( "0x%lX:%lx...\n", (uint32_t)(mcause >> 32), (uint32_t)mcause );
	printf( "0x%lX:%lx...\n", (uint32_t)(mexppc >> 32), (uint32_t)mexppc );

	while ( true )  {
		asm volatile ( "nop" );
		asm volatile ( "nop" );
	}
}


